package cloud.xlh.my_system.getter_and_setter.impl;

import cloud.xlh.infrastructure.getter_and_setter.service.Generator4MethodAccessor;
import cloud.xlh.infrastructure.getter_and_setter.service.MethodAccessor;
import cloud.xlh.infrastructure.utils.StringUtil;
import cloud.xlh.my_system.getter_and_setter.utils.DynamicCompilerAndLoad;
import cloud.xlh.my_system.getter_and_setter.utils.StringUtility;
import cloud.xlh.my_system.utils.DynamicClassLoader;
import javassist.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 *
 * @ project name : example
 * @ Author: XuLeHuang
 * @ Date: 2022/10/2 18:48
 * @ Description: 思考，分离兜底（默认）实现，提供可插拔自定义实现---3钟方式：
 * SpringBoot自定义配置，自动装配+@Component（利用扫描顺序和@ConditionalOnMissingBean）
 * 启动类的TypeExcludeFilter+@Component排除掉能注册但是排除掉的
 * 自动配置+AutoConfigurationExcludeFilter
 */
@Component("Generator4MethodAccessorByJassistImpl")
public class Generator4MethodAccessorByJassistImpl implements Generator4MethodAccessor {
    private static Logger logger = LoggerFactory.getLogger(DynamicCompilerAndLoad.class);
    private static final Map<String , MethodAccessor> classFileCache = new HashMap<>();
    //目标类信息
    private static String packageName = "cloud.xlh.my_system.getter_and_setter.gen";
    private static String implMethodAccessNameSuffix = "MethodAccess";
    private static String interfaceFullName = "cloud.xlh.infrastructure.getter_and_setter.service.MethodAccessor";

    @Override
    public MethodAccessor newMethodAccessor(Class<?> clazz) {
        //反射获取类信息
        //1.简单类名
        String simpleName = clazz.getSimpleName();
        String implMethodAccessName = new StringBuilder(packageName.concat("."))
                .append(simpleName)
                .append(implMethodAccessNameSuffix).toString();

        if (classFileCache.containsKey(implMethodAccessName)) {
            return classFileCache.get(implMethodAccessName);
        }
        //借助JavaAssist工具，进行字节码插桩
        ClassPool pool = ClassPool.getDefault();

        Field[] declaredFields = clazz.getDeclaredFields();

        CtClass ctClass = null;
        try {
            ctClass = pool.makeClass(implMethodAccessName);
            CtClass interj = pool.getCtClass(interfaceFullName);
            CtClass[] interfaces = new CtClass[]{interj};
            ctClass.setInterfaces(interfaces);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        //CtMethod setMethod = new CtMethod(CtClass.voidType, "set", new CtClass[]{pool.get(Object.class.getName()), pool.get(String.class.getName()) , pool.get(Object.class.getName())}, ctClass);
        //导入目标类所属的包
        String targetClassPackageName = clazz.getPackage().getName();
        pool.importPackage(targetClassPackageName);
        try {
            //访问目标类
            String targetClassFullName = clazz.getName();
            pool.get(targetClassFullName);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        String operatorObjectName = StringUtility.setFirstByteLowerCase(clazz
                .getSimpleName());
        //String src = getMethodBody(simpleName, operatorObjectName, declaredFields);
        String getterMethodStr = getterMethodStr(simpleName, operatorObjectName, declaredFields);
        String setterMethodStr = setterMethodStr(simpleName, operatorObjectName, declaredFields);
        CtMethod getMethod = null;
        CtMethod setMethod = null;
        try {
            getMethod = CtNewMethod.make(getterMethodStr, ctClass);
            setMethod = CtNewMethod.make(setterMethodStr, ctClass);
        } catch (CannotCompileException e) {
            logger.error("生成set方法失败......");
            e.printStackTrace();
        }
        try {
            ctClass.addMethod(getMethod);
            ctClass.addMethod(setMethod);
        } catch (CannotCompileException e) {
            logger.error("添加方法失败......");
            e.printStackTrace();
        }

        //添加无参构造
        try {
            CtConstructor defaultConstructor = new CtConstructor(
                    new CtClass[]{},
                    ctClass);
            // 创建构造函数的方法主题内容，就是 {}里面的内容
            defaultConstructor.setBody("{}");
            //添加构造器
            ctClass.addConstructor(defaultConstructor);
        } catch (Exception e) {
            logger.error("添加无参构造失败......");
            e.printStackTrace();
        }

        Class cla = this.getClass();
        // 获取class文件 所在绝对路径
        URL resource = cla.getResource("/");
        //获取class路径
        String path = "";
        try {
            path = URLDecoder.decode(resource.getPath(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            logger.error("目标class获取class路径失败......");
            e.printStackTrace();
        }
        try {
            ctClass.stopPruning(true);
            ctClass.writeFile(path);
            ctClass.defrost();
        } catch (CannotCompileException e) {
            logger.error("目标class编译失败......");
            e.printStackTrace();
        } catch (IOException e) {
            logger.error("目标class写入class路径失败......");
            e.printStackTrace();
        }
        /*DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(
                DynamicCompilerAndLoad
                        .getInstance().getClassLoader());*/
        MethodAccessor instance = null;
        Class<?> aClazz = null;
        try {
            //MyClassLoader classLoader = new MyClassLoader(path);
            //aClazz = classLoader.findClass(implMethodAccessName);
            DynamicClassLoader myUserClassLoader = new DynamicClassLoader();
            aClazz = myUserClassLoader.loadClass(implMethodAccessName);
            Constructor<?> constructor = aClazz.getConstructor();
            instance = (MethodAccessor)constructor.newInstance();
            classFileCache.put(implMethodAccessName , instance);
            return instance;
        } catch (InstantiationException e) {
            logger.error("类->{}实例化失败......" , implMethodAccessName);
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            logger.error("类->{}实例化失败......" , implMethodAccessName);
            e.printStackTrace();
        } catch (Exception e) {
            logger.error("类->{}实例化失败......" , implMethodAccessName);
        }
        return null;
    }

    private String getMethodBody(String simpleName, String operatorObjectName, Field[] declaredFields) {
        StringBuilder getterAndSetterMethodStr = new StringBuilder("");
        getterAndSetterMethodStr.append(getterMethodStr(simpleName, operatorObjectName, declaredFields));
        getterAndSetterMethodStr.append("\r\n");
        getterAndSetterMethodStr.append(setterMethodStr(simpleName, operatorObjectName, declaredFields));
        return getterAndSetterMethodStr.toString();
    }

    private String setterMethodStr(String simpleName, String operatorObjectName, Field[] declaredFields) {
        //set方法
        StringBuilder setMethodToString = new StringBuilder("public void set(Object o, String methodName,Object arg)");
        //StringBuilder setMethodToString = new StringBuilder("public void set( " + simpleName + " o, String methodName,Object arg)");
        setMethodToString = setMethodToString.append("{");
        setMethodToString.append("\r\n");
        setMethodToString.append(String.format("%1$s %2$s = (%1$s) o;", simpleName, operatorObjectName));

        for (Field declaredField : declaredFields) {
            //将首字母大写
            String name = declaredField.getName();

            String methodName = "set" + StringUtil.firstCharToUpCaseForWord(declaredField.getName());
            setMethodToString.append("\r\n");
            setMethodToString.append(String.format("if (methodName.equalsIgnoreCase(\"%1$s\") || methodName.equalsIgnoreCase(\"%2$s\")) {"
                    , methodName.toLowerCase(), methodName.substring(3)));
            setMethodToString.append("\r\n");
            //todo
            setMethodToString.append(String.format("%1$s.%2$s((%3$s) arg);}", operatorObjectName, methodName, declaredField.getType().toString().replace("class ", "")));
            setMethodToString.append("\r\n");
        }
        setMethodToString.append("}");
        return setMethodToString.toString();
    }

    private String getterMethodStr(String simpleName, String operatorObjectName, Field[] declaredFields) {
        StringBuilder getMethodToString = new StringBuilder("public Object get(Object o, String methodName){");
        getMethodToString.append("\r\n");
        getMethodToString.append(String.format("%1$s %2$s = (%1$s) o;" , simpleName , operatorObjectName));
        for (Field declaredField : declaredFields) {
            //将首字母大写
            String name = declaredField.getName();

            String methodName = "get" + StringUtil.firstCharToUpCaseForWord(declaredField.getName());
            getMethodToString.append("\r\n");
            getMethodToString.append(String.format("if (methodName.equalsIgnoreCase(\"%1$s\") || methodName.equalsIgnoreCase(\"%2$s\")) {"
                    , methodName , methodName.substring(3)));
            getMethodToString.append("\r\n");
            getMethodToString.append(String.format("return %1$s.%2$s();}" , operatorObjectName , methodName));
            getMethodToString.append("\r\n");
        }
        getMethodToString.append("return null;");
        getMethodToString.append("\r\n");
        getMethodToString.append("}");
        return getMethodToString.toString();
    }

}


